﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;

namespace Windows8Accelerator.Core.Xaml.Serialization
{
    public static class SimpleXmlSerializer
    {
        public static T Deserialize<T>(Stream stream)
        {
            var typeProperties = new Dictionary<Type, List<PropertyInfo>>();
            var typeFields = new Dictionary<Type, List<FieldInfo>>();
            var types = new List<Type>();

            XmlReaderSettings settings = new XmlReaderSettings();
            using (var reader = XmlReader.Create(stream, settings))
            {
                return ReadObject<T>(reader);
            }
        }

        public static void Serialize(Stream stream, object obj)
        {
            throw new NotImplementedException();
            //var types = new List<Type>();
            //var objects = new List<object>();

            //var headerPosition = stream.Position;
            //Write(stream, headerPosition);

            //WriteObject(stream, obj, types, objects);

            //headerPosition = stream.Position;

            //WriteTypes(stream, types);

            //stream.Position = 0;
            //Write(stream, headerPosition);

            //stream.Flush();
        }

        private static Dictionary<Type, List<PropertyInfo>> TypeProperties = new Dictionary<Type, List<PropertyInfo>>();
        private static Dictionary<Type, List<FieldInfo>> TypeFields = new Dictionary<Type, List<FieldInfo>>();

        private static bool IsPrimitive(Type type)
        {
            if (type == typeof(bool))
            {
                return true;
            }
            else if (type == typeof(char))
            {
                return true;
            }
            else if (type == typeof(byte))
            {
                return true;
            }
            else if (type == typeof(decimal))
            {
                return true;
            }
            else if (type == typeof(double))
            {
                return true;
            }
            else if (type == typeof(float))
            {
                return true;
            }
            else if (type == typeof(short))
            {
                return true;
            }
            else if (type == typeof(int))
            {
                return true;
            }
            else if (type == typeof(long))
            {
                return true;
            }
            else if (type == typeof(string))
            {
                return true;
            }
            else if (type == typeof(DateTime))
            {
                return true;
            }
            else if (type == typeof(TimeSpan))
            {
                return true;
            }
            else if (type == typeof(Guid))
            {
                return true;
            }
            else if (type == typeof(ushort))
            {
                return true;
            }
            else if (type == typeof(uint))
            {
                return true;
            }
            else if (type == typeof(ulong))
            {
                return true;
            }
            else if (type.FullName.Equals(typeof(Type).FullName))
            {
                return true;
            }
            else if (type.GetTypeInfo().IsEnum)
            {
                return true;
            }
            return false;
        }

        private static object GetValue(PropertyInfo property, object obj)
        {
            try
            {
                return property.GetValue(obj, null);
            }
            catch { }
            return null;
        }

        private static object GetValue(FieldInfo field, object obj)
        {
            try
            {
                return field.GetValue(obj);
            }
            catch { }
            return null;
        }

        private static List<PropertyInfo> GetProperties(Type type)
        {
            if (!TypeProperties.ContainsKey(type))
            {
                var members = new List<PropertyInfo>();
                foreach (var property in type.GetRuntimeProperties())
                {
                    var getMethod = property.GetMethod;
                    var setMethod = property.SetMethod;

                    if (!IgnoreDataMember(property) &&

                        property.CanRead &&

                        getMethod != null &&
                        !getMethod.IsStatic &&
                        getMethod.IsPublic &&

                        ((property.CanWrite &&
                        setMethod != null &&
                        !setMethod.IsStatic &&
                        setMethod.IsPublic) || IsListProperty(property)) &&

                        property.GetIndexParameters().Length == 0)
                    {
                        members.Add(property);
                    }
                }
                TypeProperties[type] = members;
                return members;
            }
            return TypeProperties[type];
        }

        private static bool IsListProperty(PropertyInfo property)
        {
            var typeInfo = property.PropertyType.GetTypeInfo();
            if (typeInfo.ImplementedInterfaces.Contains(typeof(IList)))
                return true;
            return false;
        }

        private static List<FieldInfo> GetFields(Type type)
        {
            if (!TypeFields.ContainsKey(type))
            {
                var members = new List<FieldInfo>();
                foreach (var field in type.GetRuntimeFields())
                {
                    if (!field.IsStatic &&
                        field.IsPublic &&
                        !IgnoreDataMember(field))
                    {
                        members.Add(field);
                    }
                }
                TypeFields[type] = members;
                return members;
            }
            return TypeFields[type];
        }

        private static bool IgnoreDataMember(MemberInfo memberInfo)
        {
            return memberInfo.GetCustomAttributes(typeof(IgnoreDataMemberAttribute), true).FirstOrDefault() != null;
        }

        public enum ReadingMode
        {
            Entity,
            Property,
            Field,
            Ignore,
            FirstEntity,
            EndProperty,
            PrimitiveEntity,
        }

        private static T ReadObject<T>(XmlReader reader)
        {
            try
            {
                var objectStack = new List<object>();
                var expectedTypeStack = new List<Type>();
                var readingModeStack = new List<ReadingMode>();
                expectedTypeStack.Add(typeof(T));
                readingModeStack.Add(ReadingMode.FirstEntity);
                var assemblyQualifiedName = typeof(T).AssemblyQualifiedName.Replace(typeof(T).FullName, typeof(T).Namespace + ".{0}");
                object currentObject = null;
                while (reader.Read())
                {
                    currentObject = objectStack.LastOrDefault();
                    var expectedObjectType = expectedTypeStack.LastOrDefault();
                    var fields = GetFields(expectedObjectType);
                    var properties = GetProperties(expectedObjectType);
                    var readingMode = readingModeStack.Last();

                    switch (reader.NodeType)
                    {
                        case XmlNodeType.Element:
                            if (readingMode != ReadingMode.Ignore)
                            {
                                if (CompareExpectedObjectType(expectedObjectType, reader.Name))
                                {
                                    if (!IsPrimitive(expectedObjectType))
                                    {
                                        objectStack.Add(CreateInstance(expectedObjectType));

                                        if (expectedObjectType.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IList)) &&
                                            expectedObjectType.GetTypeInfo().IsGenericType)
                                        {
                                            expectedTypeStack.Add(expectedObjectType.GetTypeInfo().GenericTypeArguments[0]);
                                            readingModeStack.Add(ReadingMode.Entity);
                                        }
                                    }
                                    else
                                    {
                                        readingModeStack.Add(ReadingMode.PrimitiveEntity);
                                    }
                                    continue;
                                }

                                var property = properties.FirstOrDefault((p) => p.Name.Equals(reader.Name));
                                if (property != null)
                                {
                                    readingModeStack.Add(ReadingMode.Property);
                                    expectedTypeStack.Add(property.PropertyType);
                                    objectStack.Add(property);

                                    if (!IsPrimitive(property.PropertyType))
                                    {
                                        var propertyExpectedType =
                                            property.PropertyType.IsArray ?
                                            property.PropertyType.GetElementType() :
                                            property.PropertyType;

                                        object value;
                                        if (property.CanWrite)
                                        {
                                            if (property.PropertyType.IsArray)
                                            {
                                                var genericListType = typeof(List<>).MakeGenericType(propertyExpectedType);
                                                value = CreateInstance(genericListType);
                                                expectedTypeStack.Add(genericListType);
                                            }
                                            else
                                            {
                                                value = CreateInstance(property.PropertyType);
                                                property.SetValue(currentObject, value, null);
                                            }
                                        }
                                        else
                                        {
                                            value = property.GetValue(currentObject, null);
                                        }

                                        readingModeStack.Add(ReadingMode.Entity);
                                        expectedTypeStack.Add(propertyExpectedType);
                                        objectStack.Add(value);
                                    }
                                    continue;
                                }

                                //var type = Type.GetType(string.Format(assemblyQualifiedName, reader.Name));
                                //if (type != null)
                                //{
                                //    readingModeStack.Add(ReadingMode.Property);
                                //    expectedTypeStack.Add(expectedObjectType);
                                //    objectStack.Add(currentObject);

                                //    object value = CreateInstance(type);
                                //    readingModeStack.Add(ReadingMode.Entity);
                                //    expectedTypeStack.Add(type);
                                //    objectStack.Add(value);
                                //    if (currentObject is IList)
                                //        ((IList)currentObject).Add(value);
                                //    else
                                //    {
                                //        property = objectStack[objectStack.Count - 4] as PropertyInfo;
                                //        var obj = objectStack[objectStack.Count - 5];
                                //        if (property != null && obj != null)
                                //        {
                                //            property.SetValue(obj, value, null);
                                //        }
                                //    }
                                //    continue;
                                //}
                            }

                            readingModeStack.Add(ReadingMode.Ignore);
                            expectedTypeStack.Add(expectedObjectType);
                            objectStack.Add(null);
                            break;
                        case XmlNodeType.Text:
                            if (readingMode == ReadingMode.Property)
                            {
                                var value = (object)(reader.Value);
                                var property = currentObject as PropertyInfo;
                                var obj = objectStack[objectStack.Count - 2];
                                if (property != null && obj != null)
                                {
                                    if (property.PropertyType.GetTypeInfo().IsEnum)
                                    {
                                        value = Enum.Parse(property.PropertyType, (string)value, true);
                                    }
                                    else
                                    {
                                        value = Convert.ChangeType(value, property.PropertyType, CultureInfo.InvariantCulture);
                                    }
                                    property.SetValue(obj, value, null);
                                }
                            }
                            else if (readingMode == ReadingMode.PrimitiveEntity)
                            {
                                readingModeStack.RemoveAt(readingModeStack.Count - 1);
                                var value = (object)(reader.Value);
                                if (expectedObjectType.GetTypeInfo().IsEnum)
                                {
                                    value = Enum.Parse(expectedObjectType, (string)value, true);
                                }
                                else
                                {
                                    value = Convert.ChangeType(value, expectedObjectType, CultureInfo.InvariantCulture);
                                }
                                objectStack.Add(value);
                            }
                            break;
                        case XmlNodeType.EndElement:
                            if (readingMode == ReadingMode.Property)
                            {
                                var property = currentObject as PropertyInfo;
                                var obj = objectStack[objectStack.Count - 2];
                                if (property != null && obj != null && property.PropertyType == typeof(string))
                                {
                                    if (property.GetValue(obj, null) == null)
                                        property.SetValue(obj, String.Empty, null);
                                }
                            }

                            if (readingMode == ReadingMode.Entity || readingMode == ReadingMode.PrimitiveEntity)
                            {
                                if (objectStack.Count > 1)
                                {
                                    var obj = objectStack[objectStack.Count - 2];
                                    if (obj is IList)
                                    {
                                        ((IList)obj).Add(currentObject);
                                    }
                                    else 
                                    {
                                        var property = obj as PropertyInfo;
                                        if (currentObject is IList && property != null && property.CanWrite && property.PropertyType.IsArray)
                                        {
                                            var toArrayMethod = typeof(Enumerable).GetRuntimeMethods().First((m) => m.Name.Equals("ToArray")).MakeGenericMethod(property.PropertyType.GetElementType());
                                            var value = toArrayMethod.Invoke(null, new object[]{ currentObject});
                                            property.SetValue(objectStack[objectStack.Count - 3], value, null);
                                            objectStack.RemoveAt(objectStack.Count - 1);
                                            expectedTypeStack.RemoveAt(expectedTypeStack.Count - 1);
                                            expectedTypeStack.RemoveAt(expectedTypeStack.Count - 1);
                                            readingModeStack.RemoveAt(readingModeStack.Count - 1);
                                        }
                                    }
                                }
                            }

                            if (objectStack.Count > 1)
                            {
                                objectStack.RemoveAt(objectStack.Count - 1);
                            }
                            readingModeStack.RemoveAt(readingModeStack.Count - 1);
                            expectedTypeStack.RemoveAt(expectedTypeStack.Count - 1);

                            if (readingMode == ReadingMode.Entity || readingMode == ReadingMode.PrimitiveEntity)
                            {
                                expectedObjectType = expectedTypeStack.LastOrDefault();
                                if (expectedObjectType.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IList)) &&
                                    expectedObjectType.GetTypeInfo().IsGenericType)
                                {
                                    expectedTypeStack.Add(expectedObjectType.GetTypeInfo().GenericTypeArguments[0]);
                                    readingModeStack.Add(ReadingMode.Entity);
                                }
                            }
                            break;
                    }
                }
                return (T)currentObject;
            }
            catch (Exception e)
            {
                e.ToString();
            }
            return default(T);
        }

        private static bool CompareExpectedObjectType(Type expectedObjectType, string name)
        {
            if (name.Contains(":"))
                name = name.Split(':')[1];
            if (expectedObjectType.Name.Equals(name))
                return true;
            if (expectedObjectType.Equals(typeof(int)) && name.Equals("int"))
                return true;
            if (expectedObjectType.Equals(typeof(string)) && name.Equals("string"))
                return true;
            if (expectedObjectType.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IList)) &&
                expectedObjectType.GetTypeInfo().IsGenericType &&
                name.Equals("ArrayOf" + expectedObjectType.GetTypeInfo().GenericTypeArguments[0].Name)
                )
                return true;
            return false;
        }


        private static object CreateInstance(Type type)
        {
            if (type == null ||
               type.IsArray)
                return null;

            var typeInfo = type.GetTypeInfo();
            if (typeInfo.IsAbstract ||
               typeInfo.IsValueType ||
               typeInfo.IsInterface)
                return null;
            return Activator.CreateInstance(type);
        }
    }
}
